import tkinter as tk
import os
from PIL import Image, ImageTk, ImageDraw

from config import APP_TITLE
from font_utils import find_available_fonts, find_chinese_font
from models import LabelItem, LineItem, MaskItem
from ui import UIBuilder
from handlers import EventHandlers
from file_operations import FileOperations
from menu import MenuBuilder

class ImageLabelerApp:
    def __init__(self, root):
        self.root = root
        self.root.title(APP_TITLE)

        # 图片相关属性
        self.image = None
        self.image_path = None
        self.preview_img = None
        self.tk_img = None
        self.scale = 1.0

        # 数据存储
        self.labels = []
        self.lines = []
        self.masks = []

        # 选择状态
        self.selected_label = None
        self.selected_line = None
        self.selected_mask = None

        # 标线模式
        self.line_mode = False
        self.line_type = "straight"  # "straight" 或 "polyline"
        self.line_width = 2
        self.line_color = "#000000"
        self.temp_line_points = []  # 临时存储正在绘制的线条点
        self.temp_line_canvas_ids = []  # 临时线条的canvas ID列表
        self.temp_preview_line_id = None  # 预览线ID
        self.temp_preview_helper_id = None  # 预览辅助线ID
        
        # 遮罩模式
        self.mask_mode = False
        self.mask_color = "#000000"
        self.mask_opacity = 0.5
        self.mask_drawing = False  # 是否正在绘制遮罩
        self.mask_start_pos = None  # 遮罩起始位置
        self.temp_mask_id = None  # 临时遮罩ID
        
        # 遮罩拖动状态
        self.dragging_mask = None  # 正在拖动的遮罩
        self.mask_drag_offset = None  # 拖动偏移量
        self.resizing_mask = None  # 正在调整大小的遮罩
        self.resize_handle = None  # 当前调整手柄 ('tl', 'tr', 'bl', 'br')
        self.resize_start_pos = None  # 调整开始位置
        
        # 画布拖动状态
        self.canvas_drag_start = None
        self.canvas_drag_mode = False
        
        # 控制点拖拽状态
        self.dragging_point = None
        
        # 标注拖动状态
        self.label_drag_offset = (0, 0)  # 记录点击位置与标注位置的偏移
        
        # 高质量线条渲染缓存
        self._line_images = []

        # 字体设置
        self.available_fonts = find_available_fonts()
        self.system_font = find_chinese_font()
        if self.system_font and self.system_font not in self.available_fonts:
            self.available_fonts.insert(0, self.system_font)
        self.current_font_path = self.system_font or (self.available_fonts[0] if self.available_fonts else None)

        # 初始化组件
        self.ui_builder = UIBuilder(self)
        self.event_handlers = EventHandlers(self)
        self.file_ops = FileOperations(self)
        self.menu_builder = MenuBuilder(self)

        # 构建界面和绑定事件
        self.menu_builder.build_menu()
        self.ui_builder.build_ui()
        self.event_handlers.bind_events()

    # 委托方法 - 事件处理
    def on_canvas_click(self, event):
        return self.event_handlers.on_canvas_click(event)
    
    def on_canvas_drag(self, event):
        return self.event_handlers.on_canvas_drag(event)
    
    def on_canvas_release(self, event):
        return self.event_handlers.on_canvas_release(event)
    
    def on_middle_click(self, event):
        return self.event_handlers.on_middle_click(event)
    
    def on_middle_drag(self, event):
        return self.event_handlers.on_middle_drag(event)
    
    def on_middle_release(self, event):
        return self.event_handlers.on_middle_release(event)
    
    def on_mouse_move(self, event):
        return self.event_handlers.on_mouse_move(event)
    
    def on_mouse_wheel(self, event):
        return self.event_handlers.on_mouse_wheel(event)
    
    def on_label_list_select(self, event):
        return self.event_handlers.on_label_list_select(event)
    
    def on_line_list_select(self, event):
        return self.event_handlers.on_line_list_select(event)
    
    def clear_selection(self, event=None):
        return self.event_handlers.clear_selection(event)
    
    def choose_color(self):
        return self.event_handlers.choose_color()
    
    def add_label(self):
        return self.event_handlers.add_label()
    
    def delete_selected(self):
        return self.event_handlers.delete_selected()
    
    def clear_all_annotations(self):
        return self.event_handlers.clear_all_annotations()
    
    def update_selected_label(self):
        return self.event_handlers.update_selected_label()
    
    def update_selected_line(self):
        return self.event_handlers.update_selected_line()
    
    def toggle_mask_mode(self):
        return self.event_handlers.toggle_mask_mode()
    
    def on_mask_list_select(self, event):
        return self.event_handlers.on_mask_list_select(event)
    
    def update_selected_mask(self):
        return self.event_handlers.update_selected_mask()

    # 核心功能方法
    def fit_to_window(self):
        """调整图片适应窗口大小"""
        if not self.image:
            return
        cw = self.canvas.winfo_width()
        ch = self.canvas.winfo_height()
        if cw <= 1 or ch <= 1:
            self.root.update()
            cw = self.canvas.winfo_width()
            ch = self.canvas.winfo_height()
        iw, ih = self.image.size
        self.scale = min(cw/iw, ch/ih)
        self.update_preview()
        self.update_zoom_label()

    def update_preview(self):
        """更新预览画面"""
        if not self.image:
            return
            
        # 清理线条图像缓存
        self.clear_line_image_cache()
        
        iw, ih = self.image.size
        new_size = (max(1, int(iw*self.scale)), max(1, int(ih*self.scale)))
        self.preview_img = self.image.resize(new_size, Image.LANCZOS)
        self.tk_img = ImageTk.PhotoImage(self.preview_img)
        
        # 清空画布
        self.canvas.delete("all")
        
        # 创建图像
        self.canvas.create_image(0, 0, anchor=tk.NW, image=self.tk_img)
        
        # 按层级绘制：底层图像 → 遮罩层 → 标注层
        # 1. 遮罩层（中间层）
        for mask in self.masks:
            self._draw_mask(mask)
        # 2. 线条标注（上层）
        for ln in self.lines:
            self._draw_line(ln)
        # 3. 文字标注（最上层）
        for lbl in self.labels:
            self._draw_label(lbl)

        # 更新滚动区域
        self.canvas.configure(scrollregion=self.canvas.bbox("all"))

    # 绘制相关方法

    def _draw_label(self, lbl):
        """绘制文字标注"""
        x = int(lbl.x * self.scale)
        y = int(lbl.y * self.scale)
        
        # 使用实际的字体文件而不是系统字体名
        if lbl.font_path and os.path.exists(lbl.font_path):
            try:
                from PIL import ImageFont
                # 创建PIL字体对象用于测量，但仍使用tkinter字体显示
                pil_font = ImageFont.truetype(lbl.font_path, max(1, int(lbl.font_size * self.scale)))
                # 对于tkinter，我们使用字体文件名作为family（这可能不完美，但是一个权宜之计）
                font_family = os.path.basename(lbl.font_path).split('.')[0]
                tk_font = (font_family, max(1, int(lbl.font_size*self.scale)), "bold" if lbl.bold else "normal")
            except:
                # 如果字体加载失败，使用默认字体
                tk_font = ("Arial", max(1, int(lbl.font_size*self.scale)), "bold" if lbl.bold else "normal")
        else:
            tk_font = ("Arial", max(1, int(lbl.font_size*self.scale)), "bold" if lbl.bold else "normal")
        
        cid = self.canvas.create_text(x, y, text=lbl.text, fill=lbl.color, anchor=tk.NW, font=tk_font)
        if self.selected_label == lbl:
            bbox = self.canvas.bbox(cid)
            if bbox:
                # 简单的选择框，不影响文字显示
                padding = 2
                x1, y1, x2, y2 = bbox
                self.canvas.create_rectangle(x1-padding, y1-padding, x2+padding, y2+padding, 
                                           outline="#007acc", width=2, dash=(4, 4))

    def _draw_line(self, line):
        """绘制线条标注"""
        if not line.points or len(line.points) < 2:
            return
            
        scaled_width = max(1, int(line.width * self.scale))
        
        # 使用更高精度的坐标，减少锯齿
        if line.line_type == "straight":
            # 直线：连接第一个和最后一个点
            x1 = line.points[0][0] * self.scale
            y1 = line.points[0][1] * self.scale
            x2 = line.points[-1][0] * self.scale
            y2 = line.points[-1][1] * self.scale
            
            # 使用多重绘制技术减少锯齿
            self._draw_antialiased_line(x1, y1, x2, y2, line.color, scaled_width)
        
        elif line.line_type == "polyline":
            # 折线：依次连接所有点
            for i in range(len(line.points) - 1):
                x1 = line.points[i][0] * self.scale
                y1 = line.points[i][1] * self.scale
                x2 = line.points[i + 1][0] * self.scale
                y2 = line.points[i + 1][1] * self.scale
                
                # 使用多重绘制技术减少锯齿
                self._draw_antialiased_line(x1, y1, x2, y2, line.color, scaled_width)
        
        # 如果是选中的线条，绘制简单的选择标记
        if self.selected_line == line:
            # 只绘制控制点，不改变线条样式
            for point in line.points:
                x = int(point[0] * self.scale)
                y = int(point[1] * self.scale)
                # 简单的蓝色控制点
                self.canvas.create_oval(x-4, y-4, x+4, y+4, outline="#007acc", width=2, fill="white")

    def _draw_mask(self, mask):
        """绘制遮罩"""
        # 计算缩放后的坐标和尺寸
        x = int(mask.x * self.scale)
        y = int(mask.y * self.scale)
        width = int(mask.width * self.scale)
        height = int(mask.height * self.scale)
        
        # 计算透明度颜色
        mask_color = self._get_transparent_color(mask.color, mask.opacity)
        
        # 根据透明度决定是否使用stipple
        if mask.opacity >= 1.0:
            # 完全不透明，不使用stipple
            mask_id = self.canvas.create_rectangle(x, y, x + width, y + height,
                                                  fill=mask_color, outline="")
        else:
            # 有透明度，使用stipple增强透明效果
            mask_id = self.canvas.create_rectangle(x, y, x + width, y + height,
                                                  fill=mask_color, outline="", stipple="gray75")
        
        # 如果是选中的遮罩或正在拖动/调整的遮罩，绘制选择标记
        if (self.selected_mask == mask or 
            self.dragging_mask == mask or 
            self.resizing_mask == mask):
            # 绘制选择边框
            self.canvas.create_rectangle(x, y, x + width, y + height,
                                        outline="#007acc", width=2, dash=(5, 5))
            # 绘制调整手柄
            self._draw_mask_handles(x, y, width, height)

    def _draw_mask_handles(self, x, y, width, height):
        """绘制遮罩调整手柄"""
        handle_size = 6
        # 四个角的手柄
        handles = [
            (x, y),  # 左上
            (x + width, y),  # 右上
            (x, y + height),  # 左下
            (x + width, y + height)  # 右下
        ]
        
        for hx, hy in handles:
            self.canvas.create_rectangle(hx - handle_size//2, hy - handle_size//2,
                                        hx + handle_size//2, hy + handle_size//2,
                                        fill="#007acc", outline="white", width=1)
    
    def _check_mask_handle_collision(self, x, y, mask):
        """检查是否点击了遮罩的调整手柄"""
        if not mask:
            return None
            
        # 计算缩放后的坐标和尺寸
        mx = int(mask.x * self.scale)
        my = int(mask.y * self.scale)
        mw = int(mask.width * self.scale)
        mh = int(mask.height * self.scale)
        
        handle_size = 6
        detection_range = handle_size
        
        # 四个角的手柄位置
        handles = {
            'tl': (mx, my),  # 左上
            'tr': (mx + mw, my),  # 右上
            'bl': (mx, my + mh),  # 左下
            'br': (mx + mw, my + mh)  # 右下
        }
        
        for handle_name, (hx, hy) in handles.items():
            if abs(x - hx) <= detection_range and abs(y - hy) <= detection_range:
                return handle_name
        
        return None
    
    def _check_mask_collision(self, x, y):
        """检查是否点击了遮罩"""
        for mask in reversed(self.masks):  # 从上层开始检查
            mx = int(mask.x * self.scale)
            my = int(mask.y * self.scale)
            mw = int(mask.width * self.scale)
            mh = int(mask.height * self.scale)
            
            if mx <= x <= mx + mw and my <= y <= my + mh:
                return mask
        
        return None

    def _get_transparent_color(self, color, opacity):
        """获取透明度颜色（通过颜色混合模拟）"""
        try:
            # 解析颜色
            if color.startswith('#'):
                r, g, b = int(color[1:3], 16), int(color[3:5], 16), int(color[5:7], 16)
                # 与白色背景混合模拟透明度
                # opacity=1.0时完全不透明（原色），opacity=0.0时完全透明（白色）
                r = int(r * opacity + 255 * (1 - opacity))
                g = int(g * opacity + 255 * (1 - opacity))
                b = int(b * opacity + 255 * (1 - opacity))
                return f"#{r:02x}{g:02x}{b:02x}"
        except:
            pass
        return color

    def _draw_antialiased_line(self, x1, y1, x2, y2, color, width):
        """使用PIL实现专业级抗锯齿线条"""
        import math
        
        # 计算线条边界框
        margin = max(10, width * 2)  # 足够的边距
        min_x = min(x1, x2) - margin
        max_x = max(x1, x2) + margin
        min_y = min(y1, y2) - margin
        max_y = max(y1, y2) + margin
        
        # 创建高分辨率临时图像（4倍超采样）
        scale_factor = 4
        temp_width = int((max_x - min_x) * scale_factor)
        temp_height = int((max_y - min_y) * scale_factor)
        
        if temp_width <= 0 or temp_height <= 0 or temp_width > 4000 or temp_height > 4000:
            # 如果尺寸异常，回退到传统方法
            self.canvas.create_line(x1, y1, x2, y2, fill=color, width=width,
                                   smooth=True, capstyle=tk.ROUND, joinstyle=tk.ROUND)
            return
        
        try:
            # 创建RGBA图像用于抗锯齿
            temp_img = Image.new('RGBA', (temp_width, temp_height), (0, 0, 0, 0))
            draw = ImageDraw.Draw(temp_img)
            
            # 转换坐标到临时图像坐标系
            temp_x1 = (x1 - min_x) * scale_factor
            temp_y1 = (y1 - min_y) * scale_factor
            temp_x2 = (x2 - min_x) * scale_factor
            temp_y2 = (y2 - min_y) * scale_factor
            temp_width_scaled = max(1, int(width * scale_factor))
            
            # 使用PIL的高质量线条绘制
            draw.line([(temp_x1, temp_y1), (temp_x2, temp_y2)], 
                      fill=color, width=temp_width_scaled)
            
            # 缩小图像以获得抗锯齿效果
            final_img = temp_img.resize((int(temp_width/scale_factor), 
                                        int(temp_height/scale_factor)), 
                                       Image.LANCZOS)  # 高质量缩放算法
            
            # 转换为tkinter可用的格式
            photo = ImageTk.PhotoImage(final_img)
            
            # 在canvas上绘制
            self.canvas.create_image(min_x, min_y, anchor=tk.NW, image=photo)
            
            # 保存引用防止垃圾回收
            if not hasattr(self, '_line_images'):
                self._line_images = []
            self._line_images.append(photo)
            
        except Exception as e:
            print(f"高质量渲染失败，回退到传统方法: {e}")
            # 回退到传统方法
            self.canvas.create_line(x1, y1, x2, y2, fill=color, width=width,
                                   smooth=True, capstyle=tk.ROUND, joinstyle=tk.ROUND)

    def _get_background_color(self):
        """获取背景颜色"""
        try:
            return self.canvas.cget("bg")
        except:
            return "white"

    def _blend_color(self, color1, color2, ratio):
        """混合两种颜色"""
        try:
            # 解析颜色
            if color1.startswith('#'):
                r1, g1, b1 = int(color1[1:3], 16), int(color1[3:5], 16), int(color1[5:7], 16)
            else:
                r1, g1, b1 = 0, 0, 0
                
            if color2.startswith('#'):
                r2, g2, b2 = int(color2[1:3], 16), int(color2[3:5], 16), int(color2[5:7], 16)
            else:
                r2, g2, b2 = 255, 255, 255
            
            # 混合
            r = int(r1 * (1 - ratio) + r2 * ratio)
            g = int(g1 * (1 - ratio) + g2 * ratio)
            b = int(b1 * (1 - ratio) + b2 * ratio)
            
            return f"#{r:02x}{g:02x}{b:02x}"
        except:
            return color1

    def clear_line_image_cache(self):
        """清理线条图像缓存"""
        if hasattr(self, '_line_images'):
            self._line_images.clear()

    # 列表刷新方法
    def refresh_label_list(self, select_last=False):
        """刷新标注列表"""
        import os
        self.label_list.delete(0, tk.END)
        for i, lbl in enumerate(self.labels):
            tag = "加粗" if lbl.bold else "常规"
            # 获取字体名称
            font_name = "默认字体"
            if lbl.font_path:
                font_name = os.path.basename(lbl.font_path)
                # 去掉文件扩展名，只保留字体名称
                if '.' in font_name:
                    font_name = font_name.rsplit('.', 1)[0]
            
            # 显示格式：序号. 文字内容 [字体名] (字号px, 样式)
            display_text = f"{i+1}. {lbl.text} [{font_name}] ({lbl.font_size}px, {tag})"
            self.label_list.insert(tk.END, display_text)
        if select_last and self.labels:
            self.label_list.select_set(len(self.labels)-1)
            self.on_label_list_select(None)

    def refresh_line_list(self, select_last=False):
        """刷新标线列表"""
        import os
        self.line_list.delete(0, tk.END)
        for i, ln in enumerate(self.lines):
            # 获取线条类型显示名称
            type_name = "直线" if ln.line_type == "straight" else "折线"
            
            # 获取起点和终点信息
            if ln.points and len(ln.points) >= 2:
                start_point = f"({int(ln.points[0][0])},{int(ln.points[0][1])})"
                end_point = f"({int(ln.points[-1][0])},{int(ln.points[-1][1])})"
                
                # 显示格式：序号. 类型 起点→终点 [粗细px]
                display_text = f"{i+1}. {type_name} {start_point}→{end_point} [{ln.width}px]"
            else:
                # 兼容旧数据
                display_text = f"{i+1}. {type_name} ({int(ln.x1)},{int(ln.y1)})→({int(ln.x2)},{int(ln.y2)}) [{ln.width}px]"
            
            self.line_list.insert(tk.END, display_text)
        if select_last and self.lines:
            self.line_list.select_set(len(self.lines)-1)
            self.on_line_list_select(None)

    def refresh_mask_list(self, select_last=False):
        """刷新遮罩列表"""
        self.mask_list.delete(0, tk.END)
        for i, mask in enumerate(self.masks):
            # 显示格式：序号. 遮罩 (x,y) 尺寸:w×h [透明度%]
            display_text = f"{i+1}. 遮罩 ({int(mask.x)},{int(mask.y)}) 尺寸:{int(mask.width)}×{int(mask.height)} [{int(mask.opacity*100)}%]"
            self.mask_list.insert(tk.END, display_text)
        
        # 选择最后一个项目（如果需要）
        if select_last and self.masks:
            self.mask_list.selection_set(len(self.masks) - 1)
            self.mask_list.see(len(self.masks) - 1)

    def update_zoom_label(self):
        """更新缩放比例显示"""
        if hasattr(self, 'zoom_label'):
            zoom_percent = int(self.scale * 100)
            self.zoom_label.configure(text=f"缩放: {zoom_percent}%")

def main():
    root = tk.Tk()
    app = ImageLabelerApp(root)
    root.mainloop()


if __name__ == "__main__":
    main()

